package lsp;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.atomic.AtomicBoolean;

import api.LspClientInterface;
import api.LspParams;

public class LspClient implements LspClientInterface {
	private String host;
	private int port;
	private LspParams params;
	private short connId = 0;
	private DatagramSocket ds;
	private LinkedBlockingDeque<LspMessage> entrada;
	private LinkedBlockingDeque<LspMessage> saida;
	private short seqNumber = 0;
	private AtomicBoolean epochExpired = new AtomicBoolean(false);
	private AtomicBoolean epochLimitExpired = new AtomicBoolean(false);

	public LspClient(String host, int port, LspParams params)
			throws IOException {
		this.host = host;
		this.port = port;
		this.params = params == null ? new LspParams() : params;
		this.entrada = new LinkedBlockingDeque<LspMessage>();
		this.saida = new LinkedBlockingDeque<LspMessage>();
		this.ds = new DatagramSocket();

		InetAddress servidor = InetAddress.getByName(this.host);

		Thread epochTrigger = new Thread(new EpochTrigger(this.params,
				this.epochExpired, this.epochLimitExpired));
		Thread inPackageHandler = new Thread(new InPackageHandler(this.ds,
				this.entrada, this.saida, this.epochLimitExpired, servidor));
		Thread outPackageHandler = new Thread(
				new OutPackageHandler(this.ds, this.saida, this.epochExpired,
						servidor, this.connId, this.port));

		epochTrigger.start();
		inPackageHandler.start();
		outPackageHandler.start();

		try {
			LspMessage mensagem = new LspMessage((short) 0, (short) 0,
					(short) 0, "".getBytes());
			this.saida.putFirst(mensagem); // Adiciona Connect
			while (entrada.isEmpty())
				if (epochLimitExpired.get())
					throw new CannotConnectServer("");
			this.connId = entrada.takeLast().getConnId(); // Remove Ack do
															// Connect
		} catch (InterruptedException e) {
			e.printStackTrace();
			throw new LostConectionException("");
		}
	}

	@Override
	public short getConnId() {
		// TODO Auto-generated method stub
		return this.connId;
	}

	@Override
	public byte[] read() {
		// TODO Auto-generated method stub
		while (true) {
			if (epochLimitExpired.get())
				return null;
			if (!entrada.isEmpty())
				return this.entrada.pollLast().getPayload();
		}

		// byte[] resposta = new byte[1000];
		//
		// dprequest = new DatagramPacket(resposta, resposta.length);
		// try {
		// while (true) {
		// ds.receive(dprequest);
		// break;
		// }
		//
		// } catch (IOException e) {
		// // TODO Auto-generated catch block
		// e.printStackTrace();
		// }
		// LspMessage lmr = new LspMessage(dprequest.getData());
		// return lmr.getPayload();

	}

	@Override
	public void write(byte[] payload) throws IOException {
		// TODO Auto-generated method stub
		// System.out.println(new String(payload));
		if (epochLimitExpired.get())
			throw new LostConectionException("");
		// this.seqNumber = (short) (this.seqNumber + 1);
		LspMessage mensagem = new LspMessage((short) 1, this.getConnId(),
				nextSeqNumber(), payload);
		this.saida.addFirst(mensagem);
		// this.saida.notify();
		// System.out.println(new String(lm.getMessage()));
		// System.out.println(lm.length());
		// try {
		// InetAddress ia = InetAddress.getByName(this.host);
		// dpreplay = new DatagramPacket(lm.getMessage(), lm.length(), ia,
		// this.port);
		// ds.send(dpreplay);
		//
		// } catch (IOException e) {
		// // TODO Auto-generated catch block
		// e.printStackTrace();
		// }

	}

	@Override
	public void close() {
		// TODO Auto-generated method stub
		// Rotina de fechamento de conexao
		// this.closed = true;
		// System.exit();
	}

	private short nextSeqNumber() {
		if (this.seqNumber == (Short.MAX_VALUE - Short.MIN_VALUE))
			this.seqNumber = 0;
		else
			this.seqNumber = (short) (this.seqNumber + 1);
		return this.seqNumber;
	}

}
